package server

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"time"

	"gitee.com/zh-jn/ohos-jsability-compile-service/agent"
)

type compileResult struct {
	jsbundle io.Reader
	err      error
	output   string
}

type workloadHandler struct {
	traceId     string
	coderReader io.Reader
	result      chan compileResult
}

func NewCompileServer(compileEnvZip, runningHomeBase string, agentNum, workloadsQueueSize int) *CompileServer {
	server := &CompileServer{
		agentNum:  agentNum,
		workloads: make(chan workloadHandler, workloadsQueueSize),
	}

	for i := 0; i < agentNum; i++ {
		go func(id int) {
			log.Printf("agent %v started", id)
			agent := agent.NewCompileAgent(fmt.Sprintf("compiler_agent_%v", id), compileEnvZip, runningHomeBase)
			if err := agent.Init(); err != nil {
				panic(fmt.Sprintf("agent %v init fail, reason: %v", id, err))
			}

			for workload := range server.workloads {
				// base64 decode
				buf, err := ioutil.ReadAll(workload.coderReader)
				if err != nil {
					log.Printf("read code err, %v", err)
					workload.result <- compileResult{
						jsbundle: nil,
						err:      err,
						output:   "",
					}
					continue
				}

				buf, err = base64.StdEncoding.DecodeString(string(buf))
				if err != nil {
					log.Printf("base64 decode err, %v", err)
					workload.result <- compileResult{
						jsbundle: nil,
						err:      err,
						output:   "",
					}
					continue
				}

				jsbundle, output, err := agent.Compile(bytes.NewBuffer(buf))
				if err != nil {
					workload.result <- compileResult{
						jsbundle: nil,
						err:      err,
						output:   output,
					}
					continue
				}

				buf, err = ioutil.ReadAll(jsbundle)
				if err != nil {
					log.Printf("read jsbundle err, %v", err)
					workload.result <- compileResult{
						jsbundle: nil,
						err:      err,
						output:   output,
					}
					continue
				}
				encodedJsBundle := base64.StdEncoding.EncodeToString(buf)

				workload.result <- compileResult{
					jsbundle: bytes.NewBuffer([]byte(encodedJsBundle)),
					err:      err,
					output:   output,
				}
			}
		}(i)
	}

	return server
}

type CompileServer struct {
	agentNum int
	// agentPool []*agent.CompileAgent
	workloads chan workloadHandler
}

func (c *CompileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	traceId := fmt.Sprintf("%v", rand.Intn(999999999))
	result := make(chan compileResult)
	workload := workloadHandler{
		traceId:     traceId,
		coderReader: r.Body,
		result:      result,
	}
	defer r.Body.Close()

	select {
	case c.workloads <- workload:
	default:
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("server too busy"))
		return
	}

	// 30 秒超时
	t := time.NewTimer(30 * time.Second)
	select {
	case res := <-result:
		if res.err != nil {
			w.WriteHeader(http.StatusBadRequest)
			w.Write([]byte(fmt.Sprintf("[%v] compile err, info: %v", traceId, res.output)))
			log.Printf("[%v] compile err, %v, info: %v", traceId, res.err, res.output)
			return
		}
		buf, err := ioutil.ReadAll(res.jsbundle)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte(fmt.Sprintf("[%v] read compile result err", traceId)))
			log.Printf("[%v] read compile result err, %v", traceId, err)
			return
		}
		w.Write(buf)
		return
	case <-t.C:
		w.WriteHeader(http.StatusRequestTimeout)
		w.Write([]byte(fmt.Sprintf("[%v] timeout waiting for compiler", traceId)))
		return
	}
}
